import fetch from 'node-fetch';
import fs from 'fs';

class GenerateCode {
	constructor() {
		this.fileSrc = "./src/api";
		this.url = "http://localhost:3000/graphql";
		this.schemaQuery = this.getGqlStr();
		this.schemaData = null;
		this.objectList = [];
		this.inputOjbectList = [];
		this.enumList = [];
		this.queryList = [];
		this.mutationList = [];
	}


	getJson() {
		return new Promise((resolve, reject) => {
			fetch(this.url, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ query: this.schemaQuery }),
			})
				.then(res => res.json())
				.then(result => {
					resolve(result.data.schemaData);
				})
				.catch(error => console.error(error));
		});
	}

	async init() {
		this.schemaData = await this.getJson();
		this.objectList = this.schemaData.types.filter(item => {
			return item.kind === "OBJECT" &&
				item.name !== "Mutation" &&
				item.name !== "Query" &&
				item.name.indexOf("__") < 0
		}
		);
		this.inputOjbectList = this.schemaData.types.filter(item => item.kind === "INPUT_OBJECT");
		this.enumList = this.schemaData.types.filter(item => item.kind === "ENUM" && item.name.indexOf("__") < 0);
		this.queryList = this.schemaData.types.filter(item => item.kind === "OBJECT" && item.name === "Query");
		this.mutationList = this.schemaData.types.filter(item => item.kind === "OBJECT" && item.name === "Mutation");

		this.saveModel();
		this.saveGql();
		this.saveAPI();
	}

	saveModel() {
		const listCode = [`/** 生成的文件，请勿修改 **/\n`];
		listCode.push(this.getOjectCode());
		listCode.push(this.getInputCode());
		listCode.push(this.getEnumCode());
		fs.writeFile(`${this.fileSrc}/api.model.ts`, listCode.join("\n"), function (err) {
			if (err) {
				return console.error(err);
			}
			console.log("The api.model.ts file was saved!");
		});
	}

	saveGql() {
		const listCode = [`/** 生成的文件，请勿修改 **/\n`];
		listCode.push(`import gql from "graphql-tag";\n`);
		listCode.push(`import * as APIModel from "./api.model";\n`);
		listCode.push(`import { Utilts } from "@/utils/utilts"\n`);
		this.queryList.forEach(item => {
			let code = this.generateGql(item);
			if (!code) return;
			listCode.push(code);
		});

		this.mutationList.forEach(item => {
			let code = this.generateGql(item, "Mutation");
			if (!code) return;
			listCode.push(code);
		});
		listCode.push(`/** 查询，变更GQL **/`);
		listCode.push(`export const GQL = {`);
		listCode.push(`\tQuery: QueryGQL,`);
		listCode.push(`\tMutation: MutationGQL`);
		listCode.push(`}`);

		fs.writeFile(`${this.fileSrc}/api.gql.ts`, listCode.join('\n'), function (err) {
			if (err) {
				return console.error(err);
			}
			console.log("The api.query.ts file was saved!");
		});
	}

	saveAPI() {
		const listCode = [`/** 生成的文件，请勿修改 **/\n`];
		listCode.push(`import { watch } from "vue";`)
		listCode.push(`import { provideApolloClient, useMutation, useQuery } from "@vue/apollo-composable";`)
		listCode.push(`import { apolloClient } from "@/utils/apolloClient";`)
		listCode.push(`import { GQL } from "./api.gql";`)
		listCode.push(`import * as APIModel from "./api.model"`)
		listCode.push(`import { Utilts } from "@/utils/utilts"\n`)
		listCode.push(this.getQueryService());
		listCode.push(this.getMutationService());
		listCode.push(`/** 查询，变更API **/`);
		listCode.push(`export const API = {`);
		listCode.push(`\tQuery: new QueryApi(),`);
		listCode.push(`\tMutation: new MutationApi(),`);
		listCode.push(`}`);

		fs.writeFile(`${this.fileSrc}/api.ts`, listCode.join("\n"), function (err) {
			if (err) {
				return console.error(err);
			}
			console.log("The api.query.ts file was saved!");
		});
	}

	getMutationService() {
		const listCode = [];
		this.mutationList.forEach(item => {
			let code = this.generateAPI(item, 2);
			if (!code) return;
			listCode.push(code);
		});
		return listCode.join("\n");
	}

	getQueryService() {
		const listCode = [];
		this.queryList.forEach(item => {
			let code = this.generateAPI(item, 1);
			if (!code) return;
			listCode.push(code);
		});
		return listCode.join("\n");
	}


	getOjectCode() {
		const listCode = [];
		this.objectList.forEach(item => {
			let code = this.generateObjectCode(item);
			if (!code) return;
			listCode.push(code);
		});

		return listCode.join("\n");
	}

	getInputCode() {
		const listCode = [];
		this.inputOjbectList.forEach(item => {
			let code = this.generateObjectCode(item, 2);
			if (!code) return;
			listCode.push(code);
		});
		return listCode.join("\n");
	}

	getEnumCode() {
		const listCode = [];
		this.enumList.forEach(item => {
			let code = this.generateEnumCode(item);
			if (!code) return;
			listCode.push(code);
		});
		return listCode.join("\n");
	}

	generateGql(myObj, name = 'Query') {
		if (myObj === null) return "";
		let code = "";
		if (myObj.description) {
			code += `/*** ${myObj.description} */\n`;
		} else {
			code += `/***\n *${name === "Query" ? "查询Gql" : "变更Gql"} \n*/\n`;
		}
		code += `class ${name}GQL {\n`;
		const list = myObj.fields;
		const getFieldArgs = (field, type = 1) => {
			if (field.args && field.args.length > 0) {
				const params = field.args.map(arg => {
					let name = type == 1 ? arg.name : "$" + arg.name
					let myType = this.getFieldType(arg.type);
					if (arg.type.kind === "NON_NULL") myType = myType + "!"
					let nameType = type === 1 ? `$${name}` : myType
					return `${name}: ${nameType}`;
				}).join(", ");
				return `(${params})`;
			} else {
				return ``;
			}
		}
		list.forEach(field => {
			const fieldName = field.name;

			if (field.description) {
				code += `\t/*** ${field.description} */`;
			}

			code += `\n\tstatic ${fieldName.toUpperCase()} = (fields: string) => {`
			code += `\n\t\tconst str = \`${name.toLowerCase()} ${fieldName}${getFieldArgs(field, 2)} {\n`;
			code += `\t\t\tdataSource: ${fieldName}${getFieldArgs(field)} {\${fields}}}\``;
			code += `\n\t\treturn gql(str);`
			code += '\n\t}\n\n'
		});
		code += `}\n`;
		return code;
	}

	getObjectEnity(typeName) {
		typeName = `${typeName.charAt(0).toUpperCase()}${typeName.slice(1)}`
		const currObject = this.objectList.find(p => p.name === typeName)
		return currObject
	}

	generateAPI(myObj, type = 1) {
		if (myObj === null) return "";
		let code = "";
		if (myObj.description) {
			code += `/**\n * ${myObj.description}\n */\n`;
		}
		if (type === 1) {
			code += `/**\n * 查询相关的API \n */\n`
		} else if (type === 2) {
			code += `/**\n * 变更相关的API \n */\n`
		}
		code += `class ${myObj.name}Api {\n`;
		const list = myObj.fields;
		list.forEach(field => {
			let fieldType = this.convertType(this.getFieldType(field.type));
			let apiModel = this.getAPIModel(fieldType);
			const fieldArgsCode = `fileds: DotNotation<${apiModel}${fieldType}>[]`

			fieldType = this.getAddList(field, fieldType);

			let fieldArgs = "";
			const fieldName = field.name;
			if (field.args && field.args.length > 0) {
				const params = field.args.map(arg => {
					let pType = this.convertType(this.getFieldType(arg.type));
					let apiModel2 = this.getAPIModel(pType);
					pType = this.getAddList(arg, pType);
					return `${arg.name}: ${apiModel2}${pType}`;
				}).join(", ");
				fieldArgs = `(${params}, \n\t\t${fieldArgsCode})`;
			} else {
				fieldArgs = `(${fieldArgsCode})`;
			}

			if (field.description) { code += `	/**\n 	* ${field.description}\n 	*/`; }

			if (fieldType) {
				code += `\n  ${fieldName}${fieldArgs}: Promise<${apiModel}${fieldType}> {\n`;
			} else {
				code += `  ${fieldName}${fieldArgs} {\n`;
			}

			if (type === 1) {
				code += this.getApiQueryBody(field);
			} else if (type === 2) {
				code += this.getApiMutationBody(field);
			}
		});
		code += `}\n\n`;
		return code;
	}

	getAddList(field, fieldType) {
		if (field.type.kind === "NON_NULL") {
			if (field.type.ofType.kind === "LIST") {
				fieldType = `${fieldType}[]`;
			}
		} else if (field.type.kind === "LIST") {
			fieldType = `${fieldType}[]`;
		}
		return fieldType;
	}

	getAPIModel(type) {
		if (this.objectList.findIndex(p => p.name === type) > -1 ||
			this.inputOjbectList.findIndex(p => p.name === type) > -1 ||
			this.enumList.findIndex(p => p.name === type) > -1) {
			return `APIModel.`
		} else {
			return "";
		}
	}

	getApiQueryBody(field) {
		let fieldArgs = "{}";
		if (field.args && field.args.length > 0) {
			fieldArgs = `{${field.args.map(arg => `${arg.name}`).join(", ")}}`;
		}
		let code = `    return new Promise((resolve, reject) => {\n`;
		code += `      const fildesStr = Utilts.ins().generateFieldString(fileds)\n`;
		code += `      const code = GQL.Query.${field.name.toUpperCase()}(fildesStr)\n`;
		code += `      const { result, loading } = provideApolloClient(apolloClient)(() =>\n`;
		code += `        useQuery(code, ${fieldArgs}, {\n`;
		code += `          fetchPolicy: 'cache-and-network',\n`;
		code += `        }))\n`;
		code += `			watch(loading, (val) => {	\n`
		code += `				if (!val && result.value) {\n`
		code += `					resolve(result?.value?.dataSource);\n`
		code += `				} else {\n`
		code += `					reject(null);\n`
		code += `			}})\n`
		code += `    });\n`;
		code += `  }\n\n`;
		return code;
	}

	getApiMutationBody(field) {
		let fieldArgs = "";
		if (field.args && field.args.length > 0) {
			fieldArgs = `{${field.args.map(arg => `${arg.name}`).join(", ")}}`;
		}
		let code = `    return new Promise(async (resolve, reject) => {\n`;
		code += `       var fildesStr = Utilts.ins().generateFieldString(fileds)\n`;
		code += `       var code = GQL.Mutation.${field.name.toUpperCase()}(fildesStr)\n`;
		code += `      const { mutate: send } = provideApolloClient(apolloClient)(() => useMutation(code));\n`;
		code += `      const result = await send(${fieldArgs});\n`;
		code += `      resolve(result?.data?.dataSource);\n`;
		code += `    });\n`;
		code += `  }\n\n`;
		return code;
	}

	generateEnumCode(myEnum) {
		if (myEnum === null) return "";
		let code = "";
		if (myEnum.description) {
			code += `/**\n * ${myEnum.description}\n */\n`;
		}
		code += `export enum ${myEnum.name} {\n`;
		myEnum.enumValues.forEach(value => {
			code += `  ${value.name} = "${value.name}",\n`;
		});
		code += `}\n\n`;
		return code;
	}

	generateObjectCode(myObj, type = 1) {
		if (myObj === null) return "";
		let code = "";
		if (myObj.description) {
			code += `/**\n * ${myObj.description}\n */\n`;
		}
		code += `export interface ${myObj.name} {\n`;
		var list = myObj.fields;
		if (type === 2) list = myObj.inputFields;
		if (!list || list.length === 0) return "";
		list.forEach(field => {
			let fieldType = this.convertType(this.getFieldType(field.type));
			// 处理LIST字段加上[]
			if (field.type.kind === "NON_NULL") {
				if (field.type.ofType.kind === "LIST") {
					fieldType = `${fieldType}[]`;
				}
			} else if (field.type.kind === "LIST") {
				fieldType = `${fieldType}[]`;
			}
			let fieldArgs = "";
			if (field.args && field.args.length > 0) {
				fieldArgs = `(${field.args.map(arg => `${arg.name}: ${this.getFieldType(arg.type)}`).join(", ")})`;
			}
			const fieldName = field.name;
			code += `  ${fieldName}${fieldArgs}?: ${fieldType};\n`;
		});
		code += `}\n\n`;
		return code;
	}

	getFieldType(type) {
		if (type.kind === "NON_NULL") {
			return `${this.getFieldType(type.ofType)}`;
		} else if (type.kind === "LIST") {
			return `${this.getFieldType(type.ofType)}`;
		} else if (type.kind === "SCALAR") {
			return type.name;
		} else if (type.kind === "ENUM") {
			return `${type.name}`;
		} else if (type.kind === "INPUT_OBJECT") {
			return type.name;
		} else if (type.kind === "OBJECT") {
			return type.name;
		} else {
			return "unknown";
		}
	}

	convertType(oftype) {
		switch (oftype) {
			case "BigInt":
			case "Int":
				return "number";
			case "String":
				return "string";
			case "Boolean":
				return "boolean";
			case "Float":
				return "number";
			default:
				return oftype;
		}
	}

	getGqlStr() {
		return `query {
				schemaData: __schema {
					types {
						...TypeRef
						description
						inputFields {
							name
							description
							type {
								...TypeRef
							}
						}
						fields {
							name
							description
							args {
								name
								type {
									...TypeRef
								}
							}
							type {
								...TypeRef
							}
						}
						enumValues {
							name
							description
						}
					}
				}
			}

			fragment TypeRef on __Type {
				kind
				name
				ofType {
					kind
					name
					ofType {
						kind
						name
						ofType {
							kind
							name
							ofType {
								kind
								name
							}
						}
					}
				}
		}`
	}
}

new GenerateCode().init();

